;; EX-17.scm - Note based sample playback instrument.

;; A self deleting synth definition to playback a memory resident
;; sample with control rate modifiable playback parameters.  The
;; duration control, dur, is a zero-one value based on the length of
;; the sample buffer.  The rate of sample playback is taken into
;; account when deriving the actual duration.  The unity envelope
;; exists here only to make the synth self delete.

(synthdef-send
 (synthdef
  "play-sample"
  ((out 0) (buf 0) (rate 1) (start 0) (pan 0) (gain 1) (dur 1))
  (let ((dur* (FDiv (Mul (FDiv (BufFrames.ir buf) (BufSampleRate.ir buf)) dur) 
		    rate)))
    (Out.ar out (Pan2.ar
		 (Mul (PlayBuf.ar 1 buf rate 0 start 0)
		      (EnvGen.kr envelope: (Env.bp '(0 1 1 1) dur*)
				 doneAction: 2))
		 pan gain)))))

;; A procedure to initiate a sample playback.  Since the play-sample
;; synth is self deleting and we have no interest in modifying
;; parameters after the synth is running, use the anonymous synth id
;; of negative one.

(define (play-sample group out buf rate pan gain dur)
  (-> "/s_new" "play-sample" -1 1 group
      "out" out "buf" buf "rate" rate "pan" pan 
      "gain" gain "dur" dur))

;; A procedure that uses the ftable-* system to manage the sound file
;; buffer.

(define-procedure (play-sound-file (group 1) (out 0) file-name 
				   (rate 1) (pan 0) (gain 1) (dur 1))
  (let ((b (make-buffer/shared file-name)))
    (play-sample group out b rate pan gain dur)
    (buffer/shared-release b)))

;; Set the tempo, in pulses-per-minute.

(define t (make-tschedule 90))

;; Create two groups, one for the signal generation synths, ie. the
;; play-sample synth, and another for inplace signal processing.

(begin
 (define signal-group 10)
 (define process-group 11)
 (-> "/g_new" signal-group 1 1)
 (-> "/g_new" process-group 1 1))

;; Test sample player.

(play-sound-file signal-group 0 "audio/metal.wav" 1 0 1 1)

;; Play a sample at every pulse location with unity gain and increment
;; at the centre of the stereo field.

(tschedule-at/next
 t (lambda (p t) 
     (play-sound-file signal-group 0 "audio/metal.wav" 1 0 1 1) 1))

;; Play a sample, with random increment, gain and stereo location
;; parameters and next pulse locations.

(tschedule-at/next
 t (lambda (p t)
     (play-sound-file signal-group 0 "audio/metal.wav" 
		      (random 1/4 2) (random -1 1) 1 1)
     (random '(1/2 1 2))))

;; Clear the schedule.  Existing synths will continue until they self
;; delete, no new samples will be cued.

(tschedule-clear! t)

;; A scalar melody.

(let ((cursor 0))
  (set-tschedule-ppm! t 60)
  (tschedule-at/next 
   t (lambda (pulse time)
       (play-sound-file 
	group: signal-group 
	out: 0 
	file-name: "audio/metal.wav" 
	rate: (expt 2.0 (/ (ref/wrap '(-7 -5 -3 -1 0 2 4 5 7 9 11 12) cursor) 
			   12))
	pan: (random -1 1)
	gain: 1 
	dur: 0.2)
       (set! cursor (+ cursor (random '(-3 -2 -1 0 1 2 3) '(1 2 6 1 6 2 1))))
       (random '(0.1 0.2 0.4) '(36 2 1)))))

;; A simple echo inplace signal processing effect. 

(synthdef-send
 (synthdef
  "echo"
  ((delay 0.2) (decay 4) (gain 1))
  (let ((in (In.ar 0 2)))
    (ReplaceOut.ar 0 (CombN.ar (Mul in gain) 0.5 delay decay 1 in)))))

;; Start the echo at the process group.

(-> "/s_new" "echo" 1002 1 process-group "gain" 1/5)

;; Randomly alter the echo gain.

(-> "/n_set" 1002 "gain" (random 1/5 4/5))

;; Delete echo synth.

(-> "/n_free" 1002)
